/*
 * Android Runner is a multiplayer GPS game fully written by Xurxo Mendez Perez
 * 
 * Copyright (C) 2009 Xurxo Mendez Perez
 *   
 * This file is part of Android Runner.
 * 
 * Android Runner is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Android Runner is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with Android Runner.  If not, see <http://www.gnu.org/licenses/>.
 */

package es.sonxurxo.androidrunner.model.service.game;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.Set;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import es.sonxurxo.androidrunner.model.persistence.game.accessor.FinishedGameRegisterAccessor;
import es.sonxurxo.androidrunner.model.persistence.game.accessor.GameAccessor;
import es.sonxurxo.androidrunner.model.persistence.game.cto.GameCTO;
import es.sonxurxo.androidrunner.model.persistence.game.entity.FinishedGameRegister;
import es.sonxurxo.androidrunner.model.persistence.game.entity.Game;
import es.sonxurxo.androidrunner.model.persistence.item.accessor.ItemAccessor;
import es.sonxurxo.androidrunner.model.persistence.item.accessor.QuestionDataAccessor;
import es.sonxurxo.androidrunner.model.persistence.item.entity.Bonus;
import es.sonxurxo.androidrunner.model.persistence.item.entity.Coin;
import es.sonxurxo.androidrunner.model.persistence.item.entity.Item;
import es.sonxurxo.androidrunner.model.persistence.item.entity.Question;
import es.sonxurxo.androidrunner.model.persistence.item.entity.QuestionData;
import es.sonxurxo.androidrunner.model.persistence.message.accessor.MessageAccessor;
import es.sonxurxo.androidrunner.model.persistence.message.entity.Message;
import es.sonxurxo.androidrunner.model.persistence.player.accessor.PlayerAccessor;
import es.sonxurxo.androidrunner.model.persistence.player.entity.Player;
import es.sonxurxo.androidrunner.model.persistence.util.exceptions.DuplicateInstanceException;
import es.sonxurxo.androidrunner.model.persistence.util.exceptions.InstanceNotFoundException;
import es.sonxurxo.androidrunner.model.service.game.to.AnswerQuestionResponseTO;
import es.sonxurxo.androidrunner.model.service.game.to.BonusTO;
import es.sonxurxo.androidrunner.model.service.game.to.CoinTO;
import es.sonxurxo.androidrunner.model.service.game.to.CreateGameTO;
import es.sonxurxo.androidrunner.model.service.game.to.FinishedGameTO;
import es.sonxurxo.androidrunner.model.service.game.to.GameTO;
import es.sonxurxo.androidrunner.model.service.game.to.GameTOCTO;
import es.sonxurxo.androidrunner.model.service.game.to.GetItemResponseTO;
import es.sonxurxo.androidrunner.model.service.game.to.InGamePlayerInfoTO;
import es.sonxurxo.androidrunner.model.service.game.to.ItemTO;
import es.sonxurxo.androidrunner.model.service.game.to.MessageTO;
import es.sonxurxo.androidrunner.model.service.game.to.ObserveGameResponseTO;
import es.sonxurxo.androidrunner.model.service.game.to.PlayerAnswerTO;
import es.sonxurxo.androidrunner.model.service.game.to.QuestionTO;
import es.sonxurxo.androidrunner.model.service.game.to.StartOrContinueGameResponseTO;
import es.sonxurxo.androidrunner.model.service.game.to.UpdateLocationResponseTO;
import es.sonxurxo.androidrunner.model.service.game.util.exception.MaxUsersReachedException;
import es.sonxurxo.androidrunner.model.service.game.util.exception.TimeOutException;
import es.sonxurxo.androidrunner.model.service.player.to.FinishedGameRankingTO;

/**
 * 
 * @author "Xurxo Mendez Perez"
 *
 */
@Service
public class GameServiceImpl implements GameService {

	@Autowired
	private GameAccessor gameAccessor;
	
	@Autowired
	private MessageAccessor messageAccessor;

	@Autowired
	private PlayerAccessor playerAccessor;

	@Autowired
	private ItemAccessor itemAccessor;
	
	@Autowired
	private QuestionDataAccessor questionDataAccessor;

	@Autowired
	private FinishedGameRegisterAccessor finishedGameRegisterAccessor;
	
	public FinishedGameRegisterAccessor getFinishedGameRegisterAccessor() {
		return finishedGameRegisterAccessor;
	}

	public void setFinishedGameRegisterAccessor(FinishedGameRegisterAccessor fgrAccessor) {
		this.finishedGameRegisterAccessor = fgrAccessor;
	}

	public GameAccessor getGameAccessor() {
		return gameAccessor;
	}

	public void setGameAccessor(GameAccessor gameAccessor) {
		this.gameAccessor = gameAccessor;
	}
	
	public ItemAccessor getItemAccessor() {
		return itemAccessor;
	}

	public void setItemAccessor(ItemAccessor itemAccessor) {
		this.itemAccessor = itemAccessor;
	}

	public MessageAccessor getMessageAccessor() {
		return messageAccessor;
	}

	public void setMessageAccessor(MessageAccessor messageAccessor) {
		this.messageAccessor = messageAccessor;
	}

	public PlayerAccessor getPlayerAccessor() {
		return playerAccessor;
	}

	public void setPlayerAccessor(PlayerAccessor playerAccessor) {
		this.playerAccessor = playerAccessor;
	}

	public QuestionDataAccessor getQuestionDataAccessor() {
		return this.questionDataAccessor;
	}

	public void setQuestionDataAccessor(QuestionDataAccessor questionDataAccessor) {
		this.questionDataAccessor = questionDataAccessor;
	}

	@Transactional(readOnly = true)
	public GameTO findGameById(long gameId) 
	throws InstanceNotFoundException {
		
		Game game = gameAccessor.find(gameId);
		return new GameTO(game);
	}

	@Transactional
	public GameTOCTO findGamesByCity(String city, int startIndex, int count) {
		GameCTO gameCTO = gameAccessor.findNotFinishedByCity(city, 
				startIndex, count);
		List<GameTO> gameTOList = new ArrayList<GameTO>();
		for (Game game:gameCTO.getGameList()) {
			gameTOList.add(new GameTO(game));
		}
		return new GameTOCTO(gameTOList, gameCTO.isHasMore());
	}

	@Transactional(readOnly = true)
	public List<String> findCitiesWithGames() {
		return gameAccessor.findCitiesWithGames();
	}
	
	@Transactional
	public GameTOCTO findGamesByLocation(int latitude, int longitude,
			int accurate, int startIndex, int count) {
		GameCTO gameCTO = gameAccessor.findNotFinishedByLocation(
				latitude, longitude, accurate, startIndex, count);
		List<GameTO> gameTOList = new ArrayList<GameTO>();
		for (Game game:gameCTO.getGameList()) {
			gameTOList.add(new GameTO(game));
		}
		return new GameTOCTO(gameTOList, gameCTO.isHasMore());
	}

	@Transactional
	public boolean joinGame(String login, long gameId) 
	throws InstanceNotFoundException, MaxUsersReachedException, 
	TimeOutException {
		
		Player player = playerAccessor.findByLogin(login);
		Game activeGame = gameAccessor.find(gameId);

		if (activeGame.getPlayers().size() < activeGame.getMaxPlayers()) {
	
			// If player was on another game
			if (player.getGame() != null) {
				Game abandonedGame = gameAccessor.find(
						player.getGame().getGameId());
				removePlayerFromInactiveGame(player, abandonedGame);
			}
			player.setMessageChanges(false);
			player.setItemChanges(false);
			activeGame.addPlayer(player);
			
			for (Item item:activeGame.getItems()) {
				if (item.getType().equals("COI") && 
						(item.getPlayersHaveMe().size() == 0)) {
					player.addItemICanSee(item);
				}
				else if (item.getType().equals("QUE")) {
					player.addItemICanSee(item);
				}
			}
				
			return true;
		}
			
		// If, while processing the request, the game became full
		else {
			throw new MaxUsersReachedException("Game " + activeGame.getGameId() + 
			" is full");
		}
	}
	
	@Transactional
	private void removePlayerFromInactiveGame(Player player, Game game) 
	throws InstanceNotFoundException {

		player.setMessageChanges(false);
		player.setItemChanges(false);
		
		player.setGame(null);
		player.setPoints(0);
		game.getPlayers().remove(player);
		
		for (Item item:game.getItems()) {
			item.getPlayersCanSeeMe().remove(player);
			player.getItemsICanSee().remove(item);
		}
		
	}

	@Transactional
	public void abandonGame(String login, long gameId)
	throws InstanceNotFoundException {

		Player player = playerAccessor.findByLogin(login);
		Game activeGame = gameAccessor.find(gameId);

		removePlayerFromInactiveGame(player, activeGame);
		
		Calendar finishTime = activeGame.getFinishDate();
		Calendar currentTime = Calendar.getInstance();

		currentTime.add(Calendar.MINUTE, 1);

		if (currentTime.compareTo(finishTime) < 0) {
		
			for (Item item:player.getItemsIHave()) {
				
				item.getPlayersHaveMe().remove(player);
				
				for (Player p:activeGame.getPlayers()) {
					if (item.getType().equals("COI")) {
						p.setItemChanges(true);
						p.getItemsICanSee().add(item);
						item.getPlayersCanSeeMe().add(p);
					}
				}
			}
			player.getItemsIHave().clear();
			if (!activeGame.isFinished()) {
				try {
					for (Player p:activeGame.getPlayers()) {
						p.setMessageChanges(true);
						Message broadcastMessage = new Message(
							player.getUserData().getLogin() + " has abandoned! Now you can take his items!", 
							Calendar.getInstance(), null, p, 
							Message.SYSTEM_PLAYER_ABANDONED_MESSAGE, activeGame);
		
						messageAccessor.create(broadcastMessage);
					}
				} catch (DuplicateInstanceException e) {
					
				}
			}
		}
	}
	
	@Transactional
	public StartOrContinueGameResponseTO startOrContinueGame(String login) 
	throws InstanceNotFoundException, TimeOutException {
		
		Player player = playerAccessor.findByLogin(login);
		StartOrContinueGameResponseTO sgrTO = new StartOrContinueGameResponseTO(player);
		if (!player.getGame().isFinished()) {

			sgrTO.setInGamePlayerInfoTOs(this.getInGamePlayerInfoTOs(player));
			sgrTO.setVisibleItems(this.getVisibleItems(player));

//			List<Message> messages = 
//				messageAccessor.findByReceiverAndGame(player, player.getGame());
//			Set<MessageTO> messageTOs = new HashSet<MessageTO>();
//			for (Message message:messages) {
//				message.setReaded(true);
//				messageTOs.add(new MessageTO(message));
//			}
//			sgrTO.setMyMessages(messageTOs);
		}
		
		return sgrTO;
	}

	@Transactional
	private void generateGetItemMessages(Player getterPlayer, 
			String typeOfItem) throws InstanceNotFoundException, 
			DuplicateInstanceException {
		
		// Generates a broadcast message
		for (Player p:getterPlayer.getGame().getPlayers()) {
			if (p.getPlayerId() != getterPlayer.getPlayerId()) {
				p.setMessageChanges(true);
				Message broadcastMessage = new Message(
						getterPlayer.getUserData().getLogin() + " has taken a " + typeOfItem + "!", 
						Calendar.getInstance(), null, p, 
						Message.SYSTEM_PLAYER_GETS_ITEM_MESSAGE, getterPlayer.getGame());
				messageAccessor.create(broadcastMessage);
			}
		}
	}

	@Transactional
	private void finishGame(Player winner, 
			GetItemResponseTO getItemResponseTO) 
	throws DuplicateInstanceException, InstanceNotFoundException {
		
		Game game = winner.getGame();
		game.setFinished(true);

		List<Player> orderedList = orderByPoints(game.getPlayers());

		for (int i=0;i<orderedList.size();i++) {
			Player player = orderedList.get(i);
			player.setTotalPoints(player.getTotalPoints() + player.getPoints());
			if ((player.getBestRank() == 0) || (player.getBestRank() > (i+1)))
				player.setBestRank(i+1);
				
			if (player.getMaxPoints() < player.getPoints())
				player.setMaxPoints(player.getPoints());
			
			FinishedGameRegister fgr = new FinishedGameRegister(
					player, game, i+1, player.getPoints(), player.getItemsIHave().size());
			finishedGameRegisterAccessor.create(fgr);
			
			player.setPoints(0);
		}

		for (Player player:game.getPlayers()) {
			for (Item item:player.getItemsICanSee()) {
				item.getPlayersCanSeeMe().remove(player);
			}
			player.getItemsICanSee().clear();
			for (Item item:player.getItemsIHave()) {
				item.getPlayersHaveMe().remove(player);
			}
			player.getItemsIHave().clear();
		}
	}
	
	@Transactional
	public GetItemResponseTO getItem(String login, long itemId, 
			InGamePlayerInfoTO inGamePlayerInfoTO)
	throws InstanceNotFoundException, DuplicateInstanceException {

		Player player = playerAccessor.findByLogin(login);
		
		// Get generic info
		GetItemResponseTO getItemResponseTO =
			new GetItemResponseTO();

		if (player.getGame().isFinished()) {
			getItemResponseTO.setHasFinished(true);
			if (player.isMessageChanges()) {
				player.setMessageChanges(false);
				getItemResponseTO.setMyMessages(
						this.getMessages(player));
			}
			return getItemResponseTO;
		}

		Item item = itemAccessor.find(itemId);
		getItemResponseTO.setInGamePlayerInfoTOs(
				this.getInGamePlayerInfoTOs(player));

		boolean getsTheItem = player.getItemsICanSee().contains(item);

		// Check if he can see the item (while processing his petition,
		// another player could have taken the item
		if (getsTheItem) {

			player.setItemChanges(true);
			// For all kind of items, remove from player sight,
			// add to player belongings and add its points
			player.getItemsICanSee().remove(item);
			player.getItemsIHave().add(item);
			item.getPlayersHaveMe().add(player);

			player.setPoints(player.getPoints() + item.getPoints());
			getItemResponseTO.setPlayerPoints(player.getPoints());

			// String for sending messages to players
			String typeOfItem = new String();

			// For Coins, remove from every player sight
			if (item.getType().equals("COI")) {
				typeOfItem = "coin";
				for (Player p:item.getPlayersCanSeeMe()) {
					p.setItemChanges(true);
					p.getItemsICanSee().remove(item);
				}
			}
			// For Questions, remove from player sight and send the 
			// question info
			else if (item.getType().equals("QUE")) {
				typeOfItem = "question";
				item.getPlayersCanSeeMe().remove(player);
				Question question = (Question) item;
				getItemResponseTO.setQuestionTO(
						new QuestionTO(question.getQuestionData().getQuestion(), question.getQuestionData().getOption1(),
								question.getQuestionData().getOption2(), question.getQuestionData().getOption3(),
								question.getQuestionData().getAnswer()));
			}
			// For Bonuses, remove from player sight
			else if (item.getType().equals("BON")) {
				typeOfItem = "bonus";
				
				item.getPlayersCanSeeMe().remove(player);
			}
			
			this.generateGetItemMessages(player, typeOfItem);
			
			// If player reaches the points to win
			if (player.getPoints() >= player.getGame().getPointsToWin()) {
				finishGame(player, getItemResponseTO);
				getItemResponseTO.setHasFinished(true);
				getItemResponseTO.setQuestionTO(null);
				getItemResponseTO.setActuallyTaken(getsTheItem);
				getItemResponseTO.getInGamePlayerInfoTOs().clear();
				if (player.isMessageChanges()) {
					player.setMessageChanges(false);
					getItemResponseTO.setMyMessages(
							this.getMessages(player));
				}
				return getItemResponseTO;
			}
		}

		getItemResponseTO.setActuallyTaken(getsTheItem);

		if (player.isItemChanges()) {
			player.setItemChanges(false);
			getItemResponseTO.setVisibleItems(
					this.getVisibleItems(player));
		}
		if (player.isMessageChanges()) {
			player.setMessageChanges(false);
			getItemResponseTO.setMyMessages(
					this.getMessages(player));
		}

		return getItemResponseTO;
	}
	
	@Transactional
	public AnswerQuestionResponseTO answerQuestion(String login, 
			PlayerAnswerTO playerAnswerTO, InGamePlayerInfoTO inGamePlayerInfoTO)
	throws InstanceNotFoundException {

		Player player = playerAccessor.findByLogin(login);
		Question question = (Question)itemAccessor.find(playerAnswerTO.getQuestionId());

		AnswerQuestionResponseTO answerQuestionResponseTO = 
			new AnswerQuestionResponseTO();
		
		if (player.getGame().isFinished()) {
			answerQuestionResponseTO.setHasFinished(true);
			if (player.isMessageChanges()) {
				player.setMessageChanges(false);
				answerQuestionResponseTO.setMyMessages(
						getMessages(player));
			}
			return answerQuestionResponseTO;
		}
		// Get generic info
		answerQuestionResponseTO.setInGamePlayerInfoTOs(
				getInGamePlayerInfoTOs(player));
		
		if (player.isMessageChanges()) {
			player.setMessageChanges(false);
			answerQuestionResponseTO.setMyMessages(
				getMessages(player));
		}
		answerQuestionResponseTO.setNumberOfBonusesOpened(
				question.getBonuses().size());

		// Player now sees the bonuses associated to the question
		player.getItemsICanSee().addAll(question.getBonuses());
		for (Bonus bonus:question.getBonuses()) {
			bonus.getPlayersCanSeeMe().add(player);
		}
		player.setItemChanges(false);
		answerQuestionResponseTO.setVisibleItems(
				this.getVisibleItems(player));

		return answerQuestionResponseTO;
	}

	@Transactional
	public UpdateLocationResponseTO updateLocation(String login, 
			InGamePlayerInfoTO inGamePlayerInfoTO, boolean checkIfFinished)
	throws InstanceNotFoundException {

		Player player = playerAccessor.findByLogin(login);
		
		UpdateLocationResponseTO updateLocationResponseTO = new UpdateLocationResponseTO();
		
		if (player.getGame().isFinished()) {
			updateLocationResponseTO.setHasFinished(true);
			if (player.isMessageChanges()) {
				player.setMessageChanges(false);
				updateLocationResponseTO.setMyMessages(
						getMessages(player));
			}
			return updateLocationResponseTO;
		}
		if (player.isMessageChanges()) {
			player.setMessageChanges(false);
			updateLocationResponseTO.setMyMessages(
					getMessages(player));
		}
		updateLocationResponseTO.setInGamePlayerInfoTOs(
				getInGamePlayerInfoTOs(player));
		
		if (player.isItemChanges()) {
			player.setItemChanges(false);
			updateLocationResponseTO.setVisibleItems(
					getVisibleItems(player));
		}

		// Update player location
		player.setLatitude(inGamePlayerInfoTO.getLatitude());
		player.setLongitude(inGamePlayerInfoTO.getLongitude());
		playerAccessor.update(player);
		
		return updateLocationResponseTO;
	}

	@Transactional
	public void playerSendsMessage(String login, 
			MessageTO messageTO)
	throws InstanceNotFoundException, DuplicateInstanceException {

		Player sender = playerAccessor.findByLogin(login);
		// If the message is not a broadcast
		if (messageTO.getReceiverLogin() != null) {
			
			Player receiver = playerAccessor.findByLogin(
					messageTO.getReceiverLogin());
			receiver.setMessageChanges(true);
			Message message = new Message();
			message.setMessageBody(messageTO.getMessageBody());
			message.setDate(Calendar.getInstance());
			message.setSender(sender);
			message.setReceiver(receiver);
			message.setGame(sender.getGame());
			message.setReaded(false);
			message.setType(messageTO.getType());
			messageAccessor.create(message);
			return;
		}
		else {
			Game game = gameAccessor.find(sender.getGame().getGameId());
			for (Player p:game.getPlayers()) {
				if (p.getPlayerId() != sender.getPlayerId()) {
					p.setMessageChanges(true);
					Message message = new Message();
					message.setMessageBody(messageTO.getMessageBody());
					message.setDate(Calendar.getInstance());
					message.setSender(sender);
					message.setReceiver(p);
					message.setGame(sender.getGame());
					message.setReaded(false);
					message.setType(messageTO.getType());
					messageAccessor.create(message);
				}
			}
		}
	}
	
	@Transactional
	private Set<Item> getVisibleItems(Player player) {
		Set<Item> visibleItems = new HashSet<Item>();
		// Get visible items' location
		for (Item item:player.getItemsICanSee()) {
			visibleItems.add(item);
		}

		return visibleItems;
	}

	@Transactional
	private Set<InGamePlayerInfoTO> getInGamePlayerInfoTOs(Player player) {

		Set<InGamePlayerInfoTO> inGamePlayerInfoTOs = new HashSet<InGamePlayerInfoTO>();
		Game game = player.getGame();
		// Get visible items' location
		for (Player p:game.getPlayers()) {
			if (player.getPlayerId() != p.getPlayerId()) {
				InGamePlayerInfoTO inGamePlayerInfoTO = new InGamePlayerInfoTO(p);
				inGamePlayerInfoTOs.add(inGamePlayerInfoTO);
			}
		}
		
		return inGamePlayerInfoTOs;
	}
	
	@Transactional
	private Set<MessageTO> getMessages(Player player) {
		
		Set<MessageTO> messages = new HashSet<MessageTO>();
		for (Message message:messageAccessor.findByReceiverAndGame(
				player, player.getGame())) {
			if (!message.isReaded()) {
				MessageTO messageTO = new MessageTO(message);
				messages.add(messageTO);
				message.setReaded(true);
			}
		}
		return messages;
	}
	
	private static List<Player> orderByPoints(Set<Player> playerSet) {

		List<Player> playerList = new ArrayList<Player>();
		if (playerSet.size() > 1) {
			Iterator<Player> it = playerSet.iterator();
			while (it.hasNext())
				playerList.add(it.next());
			for (int i=0;i<playerList.size()-1;i++) {
				if (playerList.get(i).getPoints() < playerList.get(i+1).getPoints()) {
					Player temp = playerList.get(i);
					playerList.set(i, playerList.get(i+1));
					playerList.set(i+1, temp);
				}
			}
		}
		else
			playerList.add(playerSet.iterator().next());
		return playerList;
	}
	
	@Transactional
	public GameTO createGame(CreateGameTO createGameTO) {
		try {
			Game game = new Game(createGameTO.getMaxPlayers(),
					createGameTO.getPointsToWin(), createGameTO.getCity());
			game.setFinishDate(createGameTO.getFinishDate());
			game.setFinished(false);
			game.setLatitude(createGameTO.getLatitude());
			game.setLongitude(createGameTO.getLongitude());
			game.setStartDate(createGameTO.getStartDate());
			this.gameAccessor.create(game);
			Set<Item> items = new HashSet<Item>();
			for (ItemTO itemTO : createGameTO.getItems()) {
				Item item;
				if (itemTO.getType().equals("COI")) {
					CoinTO coinTO = (CoinTO)itemTO;
					Coin coin = new Coin();
					coin.setGame(game);
					coin.setLatitude(coinTO.getLatitude());
					coin.setLongitude(coinTO.getLongitude());
					coin.setPoints(coinTO.getPoints());
					coin.setType(coinTO.getType());
					coin.setType("COI");
					items.add(coin);
					item = coin;
					this.itemAccessor.create(item);
				}
				else if (itemTO.getType().equals("QUE")) {
					QuestionTO questionTO = (QuestionTO)itemTO;
					Question question = new Question();
					question.setGame(game);
					question.setLatitude(questionTO.getLatitude());
					question.setLongitude(questionTO.getLongitude());
					question.setPoints(questionTO.getPoints());
					question.setType(questionTO.getType());
					
					List<QuestionData> questionDatas = this.questionDataAccessor.getAll();
					QuestionData randomQuestionData = questionDatas.get(new Random().nextInt(questionDatas.size()));
					question.setQuestionData(this.questionDataAccessor.find(randomQuestionData.getQuestionDataId()));
					question.setType("QUE");
					item = question;
					this.itemAccessor.create(item);
					question.setBonuses(new HashSet<Bonus>());
					for (BonusTO bonusTO : this.findBonusTOsForQuestionTO(createGameTO.getItems(), questionTO)) {
						Bonus bonus = new Bonus(game, bonusTO.getLatitude(), bonusTO.getLongitude(), question);
						bonus.setPoints(bonusTO.getPoints());
						bonus.setType("BON");
						question.getBonuses().add(bonus);
						items.add(bonus);
						item = bonus;
						this.itemAccessor.create(item);
					}
					if (question.getBonuses().size() == 0) {
						question.setBonuses(null);
					}
					items.add(question);
				}
			}
			game = this.gameAccessor.find(game.getGameId());
			return new GameTO(game);
		} catch (DuplicateInstanceException e) {
			e.printStackTrace();
			return null;
		} catch (InstanceNotFoundException e) {
			e.printStackTrace();
			return null;
		}
	}
	
	private List<BonusTO> findBonusTOsForQuestionTO(Set<ItemTO> items, QuestionTO questionTO) {
		List<BonusTO> result = new ArrayList<BonusTO>();
		for (ItemTO itemTO : items) {
			if (itemTO.getType().equals("BON") && 
					((BonusTO)itemTO).getQuestionId() == questionTO.getId()) {
				result.add((BonusTO)itemTO);
			}
		}
		return result;
	}
	
	private List<MessageTO> getUnreadMessages(Game game, List<Long> excludedIds) {
		List<MessageTO> messages = new ArrayList<MessageTO>();
		for (Message message: this.messageAccessor.findByGameAndNotInList(game, excludedIds)) {
			if (!message.isReaded()) {
				MessageTO messageTO = new MessageTO(message);
				messages.add(messageTO);
			}
		}
		return messages;
	}
	
	private List<InGamePlayerInfoTO> getAllInGamePlayerInfoTos(Game game) {
		List<InGamePlayerInfoTO> inGamePlayerInfoTOs = new ArrayList<InGamePlayerInfoTO>();

		for (Player p:game.getPlayers()) {
			InGamePlayerInfoTO inGamePlayerInfoTO = new InGamePlayerInfoTO(p);
			inGamePlayerInfoTOs.add(inGamePlayerInfoTO);
		}
		
		return inGamePlayerInfoTOs;
	}
	
	private List<ItemTO> getAllVisibleItemTos(Game game) {
		List<ItemTO> itemTOs = new ArrayList<ItemTO>();

		for (Item item : game.getItems()) {
			ItemTO itemTO = new ItemTO(item.getItemId(), item.getPoints(), 
					item.getLatitude(), item.getLongitude(), item.getType());
			itemTOs.add(itemTO);
		}
		
		return itemTOs;
	}

	@Transactional(readOnly = true)
	public ObserveGameResponseTO observeGame(long gameId, List<Long> excludedIds) throws InstanceNotFoundException {
		Game game = this.gameAccessor.find(new Long(gameId));
		List<MessageTO> messageTOs = this.getUnreadMessages(game, excludedIds);
		List<InGamePlayerInfoTO> inGamePlayerInfoTOs = this.getAllInGamePlayerInfoTos(game);
		List<ItemTO> items = this.getAllVisibleItemTos(game);
		return new ObserveGameResponseTO(game.getCity(), game.getPointsToWin(), 
				game.getMaxPlayers(), inGamePlayerInfoTOs, messageTOs, items);
	}

	@Transactional(readOnly = true)
	public List<GameTO> findActiveGames(int startIndex, int count) {
		GameCTO gameCTO = this.gameAccessor.findActiveGames(startIndex, count);
		List<GameTO> gameTOList = new ArrayList<GameTO>();
		for (Game game:gameCTO.getGameList()) {
			gameTOList.add(new GameTO(game));
		}
		return gameTOList;
	}
	
	@Transactional(readOnly = true)
	public List<GameTO> findNotFinishedGames(int startIndex, int count) {
		GameCTO gameCTO = this.gameAccessor.findNotFinishedGames(startIndex, count);
		List<GameTO> gameTOList = new ArrayList<GameTO>();
		for (Game game:gameCTO.getGameList()) {
			gameTOList.add(new GameTO(game));
		}
		return gameTOList;
	}
	
	@Transactional(readOnly = true)
	public Integer countActiveGames() {
		return this.gameAccessor.countActiveGames();
	}

	@Transactional(readOnly = true)
	public Integer countNotFinishedGames() {
		return this.gameAccessor.countNotFinishedGames();
	}

	@Transactional
	public void createQuestion(QuestionTO questionTO) throws DuplicateInstanceException {
		QuestionData questionData = new QuestionData(questionTO.getQuestion(), 
				questionTO.getOption1(), questionTO.getOption2(), 
				questionTO.getOption3(), questionTO.getRightAnswer());
		this.questionDataAccessor.create(questionData);
	}

	public List<FinishedGameTO> findFinishedGames(int startIndex, int count) {
		List<FinishedGameTO> result = new ArrayList<FinishedGameTO>();
		List<Game> gamesList = this.gameAccessor.findFinishedGames(startIndex, count);
		for (Game game : gamesList) {
			List<FinishedGameRegister> fGRegisters = this.finishedGameRegisterAccessor.findAllRegistersByGame(game);
			FinishedGameTO fGTO = new FinishedGameTO(game.getGameId());
			for (FinishedGameRegister fGR : fGRegisters) {
				fGTO.setCity(game.getCity());
				fGTO.setStartDate(game.getStartDate());
				fGTO.addLine(fGR.getPlayer().getUserData().getLogin(),
						fGR.getPoints(),
						fGR.getNumberOfItems(),
						fGR.getPosition());
			}
			result.add(fGTO);
		}
		return result;
	}

	public Integer countFinishedGames() {
		return this.gameAccessor.countFinishedGames();
	}

	public FinishedGameTO findFinishedGameById(long gameId)
			throws InstanceNotFoundException {
		Game game = this.gameAccessor.find(new Long(gameId));
		List<FinishedGameRegister> fGRegisters = this.finishedGameRegisterAccessor.findAllRegistersByGame(game);
		FinishedGameTO fGTO = new FinishedGameTO(game.getGameId());
		for (FinishedGameRegister fGR : fGRegisters) {
			fGTO.setCity(game.getCity());
			fGTO.setStartDate(game.getStartDate());
			fGTO.addLine(fGR.getPlayer().getUserData().getLogin(),
					fGR.getPoints(),
					fGR.getNumberOfItems(),
					fGR.getPosition());
		}
		return fGTO;
	}
}